"use strict";

const objectAssign = require("object-assign");
const pick = require("mout/object/pick");
const pluck = require("mout/object/pluck");
const size = require("mout/object/size");
const reject = require("mout/object/reject");
const contains = require("mout/string/contains");
const is = require("is.js");
const errorStatus = {code: 500,
                     message: "Unknown error",
                     hasError: true};
const successStatus = {code: 200,
                       message: "success",
                       hasError: false};

/**
 * Validate properties given rules.
 * @class ValidatonService
 * @example
 * let validationService = new ValidationService();
 */
/*export*/ class ValidationService {
  /**
   * Constructor.
   */
  constructor() {
    /** @type {string[]} */
    this.properties = ["name", "type", "required"];
  }

  /**
   * Register set of rules.
   * @param {object} rules
   * @return {bool} true is rules have required set of properties, false otherwise
   */
  register(rules) {
    let hasProperties = false;
    let rulesLength = this.properties.length;
    hasProperties = rulesLength === this.properties
      .filter((p) => size(reject(pluck(rules, p), x => null == x)) != 0)
      .length;
    if (hasProperties) {
      this.rules = rules;
      return true;
    }
    return false;
  }

  /**
   * Compare a property to it set of rules
   * @param {string|number|boolean} property
   * @param {object} rule
   * @return {bool} true if property fit the rule, false otherwise
   */
  _check(property, rule) {
    /* assert the required rule is a custom function */
    if (contains(rule.required, "custom")) {
      let name = rule.name;
      this._customRule({name : property}, rule.required);
      return;
    }
    if (null == property && true === rule.required) {
      this.status = errorStatus;
      this.status.message = `property ${rule.name} is required`;
      return;
    }
    if (null == property) {
      this.status = successStatus;
      return;
    }
    if (is(property).type.toLowerCase() !== rule.type.toLowerCase()) {
      this.status = errorStatus;
      this.status.message = `property ${property} is ${rule.type}`;
      return;
    }
    this.status = successStatus;
  }

  /**
   * Check a property against a custom rule
   * @param {object} property {rule.name: property}
   * @param {string} customRule function name
   */
  _customRule(property, customRule) {
    let status;
    let cbName = customRule.match(/^custom:(.*)$/)[1];
    let cb = this.rules.custom[cbName];
    let result;

    if (null == cb) {
      this.status = errorStatus;
      this.status.message = `function ${cbName} is not present in rule.custom`;
      return;
    }
    result = cb(property);
    if (false === result) {
      this.status = errorStatus;
      this.status.message = `${cbName} returned false`;
    }
    this.status = successStatus;
  }

  /**
   * Execute payload against rules.
   * @param {object} payload
   * @return {object} payload with status
   */
  execute(payload) {
    this.status = errorStatus;

    for (let rule in this.rules) {
      if (rule !== this.rules[rule].name && "custom" !== rule) {
        this.status.message = `wrong binding between rule key and rule.name (${rule}-${this.rules[rule].name})`;
        return;
      }
      this._check(payload[rule], this.rules[rule]);
    }
  }
}

module.exports = ValidationService;
